package com.bingxue.edu.management.education.service;

import cn.hutool.core.util.StrUtil;
import com.bingxue.edu.common.enums.ClassStatusEnum;
import com.bingxue.edu.common.enums.JoinTypeEnum;
import com.bingxue.edu.common.enums.OrderStatusEnum;
import com.bingxue.edu.common.enums.SaleStatusEnum;
import com.bingxue.edu.common.enums.StudentClassEnum;
import com.bingxue.edu.framework.common.model.PageQuery;
import com.bingxue.edu.framework.common.util.ValidationUtils;
import com.bingxue.edu.framework.mybatis.base.BaseService;
import com.bingxue.edu.framework.mybatis.query.QueryBuilder;
import com.bingxue.edu.management.education.mapper.ClassInfoMapper;
import com.bingxue.edu.management.education.mapper.EnrollmentMapper;
import com.bingxue.edu.management.education.model.entity.ArrangeLesson;
import com.bingxue.edu.management.education.model.entity.ClassInfo;
import com.bingxue.edu.management.education.model.entity.ClassStudent;
import com.bingxue.edu.management.education.model.entity.ClassType;
import com.bingxue.edu.management.education.model.entity.TimeTemplate;
import com.bingxue.edu.management.education.model.query.ClassQuery;
import com.bingxue.edu.management.education.model.query.ClassWithPermissionQuery;
import com.bingxue.edu.management.education.model.query.CommonClassQuery;
import com.bingxue.edu.management.education.model.req.ClassInfoCreateReq;
import com.bingxue.edu.management.education.model.req.ClassInfoUpdateReq;
import com.bingxue.edu.management.education.model.req.ClassSaleUpdateReq;
import com.bingxue.edu.management.education.model.req.SelectiveImportPreStudentReq;
import com.bingxue.edu.management.education.model.resp.ArrangeTemplateDetailResp;
import com.bingxue.edu.management.education.model.resp.ClassDetailResp;
import com.bingxue.edu.management.education.model.resp.ClassInfoResp;
import com.bingxue.edu.management.education.model.resp.ClassInfoSummaryResp;
import com.bingxue.edu.management.education.model.resp.ImportPrevStudentResp;
import com.bingxue.edu.management.education.model.resp.UnpaidStudentResp;
import com.bingxue.edu.management.shop.service.CartService;
import com.bingxue.edu.management.shop.service.OrderItemService;
import com.bingxue.edu.parent.model.req.ParentCartItemAddReq;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.query.QueryMethods;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.IntStream;

import static com.bingxue.edu.common.util.EduUtil.generateClassId;
import static com.bingxue.edu.management.education.model.entity.table.ClassInfoTableDef.CLASS_INFO;
import static com.bingxue.edu.management.education.model.entity.table.ClassroomTableDef.CLASSROOM;
import static com.bingxue.edu.management.education.model.entity.table.EnrollmentTableDef.ENROLLMENT;
import static com.bingxue.edu.management.education.model.entity.table.LessonTableDef.LESSON;
import static com.bingxue.edu.management.education.model.entity.table.StudentTableDef.STUDENT;
import static com.bingxue.edu.management.shop.model.entity.table.OrderItemTableDef.ORDER_ITEM;
import static com.bingxue.edu.management.shop.model.entity.table.OrderTableDef.ORDER;
import static com.bingxue.edu.management.system.model.entity.table.EmployeeTableDef.EMPLOYEE;
import static com.bingxue.edu.management.system.model.entity.table.OrgTableDef.ORG;
import static com.mybatisflex.core.query.QueryMethods.ifNull;
import static com.mybatisflex.core.query.QueryMethods.number;
import static com.mybatisflex.core.query.QueryMethods.sum;

/**
 * 班级管理实现类
 *
 * @author zq
 * @since 2024/08/21
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ClassInfoService extends BaseService<ClassInfoMapper, ClassInfo> {

    private final EnrollmentMapper enrollmentMapper;

    private final ArrangeTemplateService arrangeTemplateService;
    private final TimeTemplateService timeTemplateService;
    private final LessonService lessonService;
    private final ClassStudentService classStudentService;
    private final ClassTypeService classTypeService;
    private final EnrollPermissionService  enrollPermissionService;
    private final CartService cartService;
    private final OrderItemService orderItemService;

    public static QueryCondition buildCommonClassQueryCondition(CommonClassQuery query) {
        return CLASS_INFO.ID.eq(query.getClassId())
                .and(CLASS_INFO.SUBJECT.eq(query.getSubject()))
                .and(CLASS_INFO.CLASS_YEAR.eq(query.getClassYear()))
                .and(CLASS_INFO.CLASS_CATEGORY.eq(query.getClassCategory()))
                .and(CLASS_INFO.CLASS_TYPE_ID.eq(query.getClassTypeId()))
                .and(CLASS_INFO.CAMPUS_ID.eq(query.getCampusId()))
                .and(CLASS_INFO.CLASSROOM_ID.eq(query.getClassroomId()))
                .and(CLASS_INFO.TERM.eq(query.getTerm()))
                .and(CLASS_INFO.GRADE.in(query.getGrade()))
                .and(CLASS_INFO.LEAD_TEACHER_ID.eq(query.getLeadTeacherId()));
    }

    @Transactional(rollbackFor = Exception.class)
    public String create(ClassInfoCreateReq req) {
        // 初始化班级信息
        ClassInfo classInfo = initNewClassInfo(req);
        // 获取班型等级
        ClassType classType = classTypeService.getById(req.getClassTypeId());
        classInfo.setClassLevelLimit(classType.getLevel());
        // 查询排课模板信息
        ArrangeTemplateDetailResp arrangeTemplateDetail = arrangeTemplateService.getDetailById(req.getArrangeTemplateId());
        TimeTemplate timeTemplate = timeTemplateService.getById(req.getTimeTemplateId());
        List<ArrangeLesson> arrangeLessonList = arrangeTemplateDetail.getArrangeLessonList();
        classInfo.setTotalLessonCount(arrangeLessonList.size());
        classInfo.setCompletedLessonCount(0);
        this.save(classInfo);

        lessonService.createLessonBatch(classInfo, arrangeLessonList, timeTemplate);
        log.info("创建班级成功: {}", classInfo);

        if (req.getPrevClassId() != null && req.getImportPrevStudent() != null && req.getImportPrevStudent()) {
            importPrevClassStudent(classInfo);
        }
        return classInfo.getId();
    }

    /**
     * 导入前序班级学生
     */
    @Transactional(rollbackFor = Exception.class)
    public ImportPrevStudentResp importPrevClassStudent(String classId) {
        ClassInfo currClassInfo = this.getById(classId);
        ValidationUtils.throwIfNull(currClassInfo, "班级不存在");
        return importPrevClassStudent(currClassInfo);
    }

    /**
     * 导入前序班级学生
     */
    @Transactional(rollbackFor = Exception.class)
    public ImportPrevStudentResp importPrevClassStudent(ClassInfo currClassInfo) {
        ImportPrevStudentResp result = new ImportPrevStudentResp();
        ValidationUtils.throwIfNull(currClassInfo.getPrevClassId(), "前序班级未设置");
        ClassInfo prevClassInfo = this.getById(currClassInfo.getPrevClassId());
        ValidationUtils.throwIfNull(prevClassInfo, "前序班级不存在");

        List<ClassStudent> classStudentList = classStudentService.getListByClassId(currClassInfo.getPrevClassId(), StudentClassEnum.NORMAL);
        result.setTotalCount(classStudentList.size());
        // 添加到购物车
        for (ClassStudent classStudent : classStudentList) {
            try {
                ParentCartItemAddReq addReq = new ParentCartItemAddReq();
                addReq.setClassId(currClassInfo.getId());
                addReq.setStudentId(classStudent.getStudentId());
                List<Integer> lessonIndexes = switch (currClassInfo.getTerm()) {
                    case SPRING, AUTUMN -> List.of(1,2,3,4);
                    case SUMMER, WINTER -> IntStream.rangeClosed(1, currClassInfo.getTotalLessonCount()).boxed().toList();
                };
                addReq.setLessonIndexes(lessonIndexes);
                cartService.addCartItem(addReq, false);
                result.setSuccessCount(result.getSuccessCount() + 1);
            } catch (Exception e) {
                log.warn("导入前序班级学员出错: {}", e.getMessage());
                result.setErrorCount(result.getErrorCount() + 1);
            }
        }
        log.info("导入前序班级学员完成: {}", result);
        return result;
    }

    public Page<ClassInfoResp> getClassInfoPage(ClassQuery query, PageQuery pageQuery) {
        QueryWrapper queryWrapper = buildQueryWrapper(query);
        return this.getMapper().paginateWithRelationsAs(pageQuery.toPage(), queryWrapper, ClassInfoResp.class);
    }

    public List<ClassInfoResp> getClassInfoList(ClassQuery query) {
        QueryWrapper queryWrapper = buildQueryWrapper(query);
        return this.getMapper().selectListWithRelationsByQueryAs(queryWrapper, ClassInfoResp.class);
    }

    public ClassInfoSummaryResp getClassInfoSummary(ClassQuery query) {
        QueryWrapper queryWrapper = QueryBuilder.build(query)
                .select(
                        QueryMethods.count().as(ClassInfoSummaryResp::getClassCount),
                        ifNull(sum(CLASS_INFO.PLAN_STUDENT_COUNT), number(0)).as(ClassInfoSummaryResp::getPlanStudentCount),
                        ifNull(sum(CLASS_INFO.ENROLL_STUDENT_COUNT), number(0)).as(ClassInfoSummaryResp::getEnrollStudentCount),
                        ifNull(sum(CLASS_INFO.PASSED_STUDENT_COUNT), number(0)).as(ClassInfoSummaryResp::getPassedStudentCount),
                        ifNull(sum(CLASS_INFO.TOTAL_LESSON_COUNT), number(0)).as(ClassInfoSummaryResp::getTotalLessonCount),
                        ifNull(sum(CLASS_INFO.COMPLETED_LESSON_COUNT), number(0)).as(ClassInfoSummaryResp::getCompletedLessonCount)
                )
                .from(CLASS_INFO);
        return this.getOneAs(queryWrapper, ClassInfoSummaryResp.class);
    }

    public Page<ClassInfoResp> getClassInfoPageWithPermission(ClassWithPermissionQuery query, PageQuery pageQuery) {
        QueryWrapper queryWrapper = buildQueryWrapper(query)
                .where(enrollPermissionService.buildPermissionFilterCondition(query.getStudentId(), query.getSubject()));
        return this.pageAs(pageQuery.toPage(), queryWrapper, ClassInfoResp.class);
    }

    /**
     * 根据id删除班级，只有在未开课且没有学生报名的情况下才允许关班
     */
    @Transactional
    public void deleteClassById(String classId) {
        ClassInfo classInfo = this.getById(classId);
        ValidationUtils.throwIfNull(classInfo, "班级不存在");
        // 判断该班级是否有报名记录
        long enrollCount = enrollmentMapper.selectCountByCondition(ENROLLMENT.CLASS_ID.eq(classId));
        ValidationUtils.throwIf(enrollCount > 0, "该班级已有报名记录，无法删除");
        long orderItemCount = orderItemService.countValidOrderItemByClassId(classId);
        ValidationUtils.throwIf(orderItemCount > 0, "该班级已有报名报名订单，无法删除");
        // 删除该班级的课次信息
        lessonService.deleteLessonByClassId(classId);
        // 删除该班级相关联的购物车
        cartService.deleteItemByClassId(classId);
        // 修改班级状态为：关班
        classInfo.setClassStatus(ClassStatusEnum.CLOSED);
        this.updateById(classInfo);
        this.removeById(classId);
        log.info("删除班级成功：{}", classId);
    }


    /**
     * 根据id关闭班级，还没上课或者已经上了部分课次的班级也可以关闭
     */
    @Transactional(rollbackFor = Exception.class)
    public void closeClassById(String classId) {
        ClassInfo classInfo = this.getById(classId);
        ValidationUtils.throwIfNull(classInfo, "班级不存在");
        classInfo.setClassStatus(ClassStatusEnum.CLOSED);
        this.updateById(classInfo);
        // 停课
        lessonService.stopLessonByClassId(classId);
        // 删除该班级相关联的购物车
        cartService.deleteItemByClassId(classId);
        log.info("关闭班级成功：{}", classId);
    }

    /**
     * 查询班级详情
     */
    public ClassDetailResp getDetailById(String classId) {
        QueryWrapper queryWrapper = buildQueryWrapper().where(CLASS_INFO.ID.eq(classId));
        return this.getMapper().selectOneWithRelationsByQueryAs(queryWrapper, ClassDetailResp.class);
    }



    /**
     * 修改班级报名学生数量。实际审核通过人数不变，会导致未审核订单数量+1
     * @param classId 班级id
     * @param changeCount 变化数量，可为负数
     */
    @Transactional(rollbackFor = Exception.class)
    public void increaseEnrollStudentCount(String classId, int changeCount) {
        this.updateChain()
                .set(CLASS_INFO.ENROLL_STUDENT_COUNT, CLASS_INFO.ENROLL_STUDENT_COUNT.add(changeCount))
                .where(CLASS_INFO.ID.eq(classId))
                .update();
    }

    /**
     * 增加班级通过学生数量
     * @param classId 班级id
     * @param changeCount 变化数量，可为负数
     */
    @Transactional(rollbackFor = Exception.class)
    public void increasePassedStudentCount(String classId, int changeCount) {
        this.updateChain()
                .set(CLASS_INFO.PASSED_STUDENT_COUNT, CLASS_INFO.PASSED_STUDENT_COUNT.add(changeCount))
                .where(CLASS_INFO.ID.eq(classId))
                .update();
    }

    /**
     * 增加班级实际学生数量
     * @param classId 班级id
     * @param changeCount 变化数量，可为负数
     */
    @Transactional(rollbackFor = Exception.class)
    public void increaseStudentCount(String classId, int changeCount) {
            this.updateChain()
                    .set(CLASS_INFO.PASSED_STUDENT_COUNT, CLASS_INFO.PASSED_STUDENT_COUNT.add(changeCount))
                    .set(CLASS_INFO.ENROLL_STUDENT_COUNT, CLASS_INFO.ENROLL_STUDENT_COUNT.add(changeCount))
                    .where(CLASS_INFO.ID.eq(classId))
                    .update();
    }


    /**
     * 移除班级学生
     * @param studentId 学生id
     * @param classId 班级id
     * @param status 学生状态
     */
    @Transactional(rollbackFor = Exception.class)
    public void removeStudentFromClass(String studentId, String classId, StudentClassEnum status) {
        classStudentService.updateStudentClassStatus(studentId, classId, status);
        this.increaseStudentCount(classId, -1);
    }

    /**
     * 添加班级学生
     * @param studentId 学生id
     * @param classId 班级id
     * @param enrollmentId 报名id
     * @param joinType 加入方式
     */
    @Transactional(rollbackFor = Exception.class)
    public void addStudentToClass(String studentId, String classId, String enrollmentId, JoinTypeEnum joinType) {
        classStudentService.createClassStudent(studentId, classId, enrollmentId, joinType);
        if (joinType == JoinTypeEnum.ENROLL) {
            this.increasePassedStudentCount(classId, 1);
        } else if (joinType == JoinTypeEnum.CHANGE_CLASS) {
            this.increaseStudentCount(classId, 1);
        }
    }

    /**
     * 更新上架状态
     * @param req 请求入参
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateSaleStatus(ClassSaleUpdateReq req) {
        this.updateChain()
                .set(CLASS_INFO.SALE_STATUS, req.getStatus())
                .where(CLASS_INFO.ID.eq(req.getClassId()))
                .update();
        if (req.getStatus() == SaleStatusEnum.OFF) {
            cartService.deleteItemByClassId(req.getClassId());
        }
    }

    /**
     * 更新班级
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateByClassId(ClassInfoUpdateReq req, String id) {
        ClassInfo oldClass = this.getById(id);
        ValidationUtils.throwIfNull(oldClass, "班级不存在");
        if (req.getSaleStatus() == SaleStatusEnum.OFF) {
            cartService.deleteItemByClassId(id);
        }
        if (StrUtil.isNotEmpty(req.getAssistTeacherId()) && !req.getAssistTeacherId().equals(oldClass.getAssistTeacherId())) {
            lessonService.updateChain()
                    .set(LESSON.ASSIST_TEACHER_ID, req.getAssistTeacherId())
                    .where(LESSON.CLASS_ID.eq(id))
                    .update();
        }
        ClassInfo classInfo = this.copyById(req, id);
        this.updateById(classInfo);
        log.info("更新班级成功, req: {}, id: {}", req, id);
    }

    /**
     * 选择性导入前序班级学生列表
     * @param req 学生列表实体
     */
    public ImportPrevStudentResp selectiveImportPrevClassStudent(SelectiveImportPreStudentReq req, String classId) {
        ImportPrevStudentResp resp = new ImportPrevStudentResp();
        //获取对应班级信息
        ClassInfo currClassInfo = this.getById(classId);
        //根据学生id列表查询对应班级学生信息列表
        resp.setTotalCount(req.getStudentIdList().size());
        // 添加到购物车
        for (String studentId : req.getStudentIdList()) {
            try {
                ParentCartItemAddReq addReq = new ParentCartItemAddReq();
                addReq.setClassId(currClassInfo.getId());
                addReq.setStudentId(studentId);
                List<Integer> lessonIndexes = switch (currClassInfo.getTerm()) {
                    case SPRING, AUTUMN -> List.of(1,2,3,4);
                    case SUMMER, WINTER -> IntStream.rangeClosed(1, currClassInfo.getTotalLessonCount()).boxed().toList();
                };
                addReq.setLessonIndexes(lessonIndexes);
                cartService.addCartItem(addReq, false);
                resp.setSuccessCount(resp.getSuccessCount() + 1);
            } catch (Exception e) {
                log.warn("导入前序班级学员出错: {}", e.getMessage());
                resp.setErrorCount(resp.getErrorCount() + 1);
            }
        }

        log.info("导入前序班级学员完成: {}", resp);
        return resp;
    }

    /**
     * 根据班级id该班级获取报名但未支付的学员信息列表
     * @param classId 班级id
     * @return 未支付学员信息列表
     */
    public List<UnpaidStudentResp> getUnPaidStudentList(String classId) {
            QueryWrapper queryWrapper = QueryWrapper.create()
                    .select(ORDER_ITEM.ALL_COLUMNS,
                            ORDER.ID.as(UnpaidStudentResp::getOrderId),
                            ORDER.STATUS.as(UnpaidStudentResp::getStatus),
                            ORDER.PAY_OFF.as(UnpaidStudentResp::getPayOff),
                            ORDER.CREATE_TIME.as(UnpaidStudentResp::getCreateTime),
                            STUDENT.ID.as(UnpaidStudentResp::getStudentId),
                            STUDENT.NAME.as(UnpaidStudentResp::getStudentName),
                            STUDENT.AVATAR.as(UnpaidStudentResp::getAvatar),
                            STUDENT.PARENT_PHONE.as(UnpaidStudentResp::getMobilePhone),
                            ENROLLMENT.ENROLL_LESSON_COUNT.as(UnpaidStudentResp::getEnrollLessonCount))
                    .from(ORDER_ITEM)
                    .leftJoin(ORDER).on(ORDER.ID.eq(ORDER_ITEM.ORDER_ID))
                    .leftJoin(STUDENT).on(STUDENT.ID.eq(ORDER_ITEM.STUDENT_ID))
                    .leftJoin(ENROLLMENT).on(ENROLLMENT.CLASS_ID.eq(ORDER_ITEM.CLASS_ID).and(ENROLLMENT.STUDENT_ID.eq(ORDER_ITEM.STUDENT_ID)))
                    .where(ORDER_ITEM.CLASS_ID.eq(classId))
                    .and(ORDER.STATUS.in(OrderStatusEnum.PAYING,OrderStatusEnum.PENDING));
        return orderItemService.listAs(queryWrapper, UnpaidStudentResp.class);

    }

    private QueryWrapper buildQueryWrapper(ClassQuery classQuery) {
        return QueryBuilder.build(classQuery)
                .select(
                        CLASS_INFO.DEFAULT_COLUMNS,
                        CLASSROOM.NAME.as(ClassInfoResp::getClassroomName),
                        ORG.NAME.as(ClassInfoResp::getCampusName),
                        EMPLOYEE.NAME.as(ClassInfoResp::getLeadTeacherName)
                )
                .from(CLASS_INFO)
                .leftJoin(CLASSROOM).on(CLASS_INFO.CLASSROOM_ID.eq(CLASSROOM.ID))
                .leftJoin(ORG).on(CLASS_INFO.CAMPUS_ID.eq(ORG.ID))
                .leftJoin(EMPLOYEE).on(CLASS_INFO.LEAD_TEACHER_ID.eq(EMPLOYEE.ID))
                .orderBy(CLASS_INFO.ID.desc());
    }


    private QueryWrapper buildQueryWrapper() {
        return QueryWrapper.create()
                .select(
                        CLASS_INFO.DEFAULT_COLUMNS,
                        CLASSROOM.NAME.as(ClassInfoResp::getClassroomName),
                        ORG.NAME.as(ClassInfoResp::getCampusName),
                        EMPLOYEE.NAME.as(ClassInfoResp::getLeadTeacherName)
                )
                .from(CLASS_INFO)
                .leftJoin(CLASSROOM).on(CLASS_INFO.CLASSROOM_ID.eq(CLASSROOM.ID))
                .leftJoin(ORG).on(CLASS_INFO.CAMPUS_ID.eq(ORG.ID))
                .leftJoin(EMPLOYEE).on(CLASS_INFO.LEAD_TEACHER_ID.eq(EMPLOYEE.ID));
    }

    private ClassInfo initNewClassInfo(ClassInfoCreateReq req) {
        ClassInfo newClassInfo = this.copy(req);
        newClassInfo.setId(generateClassId(req));
        newClassInfo.setEnrollStudentCount(0);
        newClassInfo.setPassedStudentCount(0);
        newClassInfo.setClassStatus(ClassStatusEnum.WAITING);
        return newClassInfo;
    }
}
